package com.serviceclient.scribe;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Verb;
import org.scribe.utils.StreamUtils;

import com.servicerequest.impl.RequestLogger;
import com.servicerequest.impl.ServiceResponseImpl;
import com.servicerequest.model.Parameter;
import com.servicerequest.model.ServiceRequest;
import com.servicerequest.model.ServiceResponse;

public class ScribeServiceRequest implements ServiceRequest {
	private static final int DEFAULT_BUFFER_SIZE = 1024;

	private final OAuthRequest request;

	private final List<Parameter<String>> queryParams = new ArrayList<Parameter<String>>();
	private final List<Parameter<Object>> formParams = new ArrayList<Parameter<Object>>();

	public static class Builder {

		private final Verb verb;
		private final String url;

		private final Map<String, String> headers = new HashMap<String, String>();
		private final List<Parameter<String>> queryParams = new ArrayList<Parameter<String>>();
		private final List<Parameter<String>> formParams = new ArrayList<Parameter<String>>();

		public Builder(String method, String url) {
			if (method == null || url == null) {
				throw new IllegalArgumentException("Method and url cannot be null");
			}
			try {
				this.verb = Verb.valueOf(method.toString());
			} catch (Exception e) {
				throw new IllegalArgumentException(String.format("Invalid method '%s' for scribe request", method));
			}
			this.url = url;
		}

		public void addHeaderValues(Map<String, String> headers) {
			this.headers.putAll(headers);
		}

		public void addQueryParam(String name, String value) {
			Parameter<String> queryParameter = new Parameter<String>(name, value);
			this.queryParams.add(queryParameter);
		}

		public void addFormParam(String name, String value) {
			Parameter<String> formParameter = new Parameter<String>(name, value);
			this.formParams.add(formParameter);
		}

		public ScribeServiceRequest build() {
			return new ScribeServiceRequest(this);
		}
	}

	private ScribeServiceRequest(Builder builder) {
		this.request = new OAuthRequest(builder.verb, builder.url);

		for (Entry<String, String> entry : builder.headers.entrySet()) {
			request.addHeader(entry.getKey(), entry.getValue());
		}

		this.queryParams.addAll(builder.queryParams);
		for (Parameter<String> param : queryParams) {
			request.addQuerystringParameter(param.getName(), param.getValue());
		}

		for (Parameter<String> param : builder.formParams) {
			Parameter<Object> objectParameter = new Parameter<Object>(param.getName(), param.getValue());
			this.formParams.add(objectParameter);
			if (param.getValue() != null) {
				request.addBodyParameter(param.getName(), param.getValue());
			}
		}
	}

	@Override
	public ServiceResponse send() {

		RequestLogger.logRequest(this);
		Response response = request.send();

		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			if (response.getStream() != null) {
				byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
				int len;
				while ((len = response.getStream().read(buffer)) > -1) {
					baos.write(buffer, 0, len);
				}
				baos.flush();
			}
			InputStream stream = new ByteArrayInputStream(baos.toByteArray());
			String body = StreamUtils.getStreamContents(stream);

			InputStream copiedStream = new ByteArrayInputStream(baos.toByteArray());

			ServiceResponse apiResponse = new ServiceResponseImpl(this, response.getCode(), response.getHeaders(), body, copiedStream);
			RequestLogger.logResponse(apiResponse);
			return apiResponse;

		} catch (IOException e) {
			throw new IllegalStateException("Couldn't make copy of input stream", e);
		}
	}

	@Override
	public String getMethod() {
		return String.valueOf(request.getVerb());
	}

	@Override
	public String getUrl() {
		return request.getUrl();
	}

	@Override
	public Map<String, String> getHeaders() {
		return Collections.unmodifiableMap(request.getHeaders());
	}

	@Override
	public List<Parameter<String>> getQueryParams() {
		return Collections.unmodifiableList(queryParams);
	}

	@Override
	public List<Parameter<Object>> getFormParams() {
		return Collections.unmodifiableList(formParams);
	}

	public String getHeader(String name) {
		if (!request.getHeaders().containsKey(name)) {
			throw new IllegalArgumentException(String.format("Header '%s' doesn't exist in request", name));
		}
		return request.getHeaders().get(name);
	}
}
